上节课我们主要介绍了部署系统中各耗时环节的一些常用优化方案。课后思考题是:课程中提到了几种利用缓存的优化方案呢?如果你认真学习了课程内容,不难发现我一共提到了三种基于缓存的优化方案,它们分别是:多项目共用依赖缓存、依赖安装目录的缓存以及构建过程的持久化缓存备份。这些缓存方案不仅可以运用到传统的部署方式中,在今天介绍的容器化部署方案中也有各自的用武之地。
下面我就来介绍本节课的第一个话题:什么是容器化呢?
容器化(Containerization)通常是指以 Docker 技术为代表,将操作系统内核虚拟化的技术。和传统的虚拟机相比,容器化具有占用空间更小、性能开销更低、启动更快、支持弹性伸缩以及支持容器间互联等优势。
下面介绍 Docker 的几个基本概念。
通常我们提到的 Docker 是指运行在 Linux/Windows/macOS 中开源的虚拟化引擎,用于创建、管理和编排容器(此外,Docker 也是发布和维护该开源项目的公司名称)。
Docker 中的镜像(Image)是指用于创建容器实例的基础虚拟化模板。对于开发人员来说,可以把镜像理解为编程语言中的类:通过一个镜像可以创建多个容器实例,镜像之间也存在继承关系。通过 Docker 引擎可以构建、删除镜像,还可以将本地镜像 push 到远程仓库或者从远程拉取。例如一个基于 node:14 的镜像,在创建时不光包含了运行 node14 版本所需的 Linux 系统环境,还包含了额外打入到镜像内的 Yarn 程序。
容器(Container)是 Docker 中的核心功能单元。通常一个容器内包含了一个或多个应用程序以及运行它们所需要的完整相关环境依赖。例如,基于上面例子中的 node:14 镜像,就可以创建出对应版本 NodeJS 的独立运行环境。通过 Docker 引擎可以对容器进行创建、删除、停止、恢复、与容器交互等操作。
通常情况下容器内的基础数据来自创建容器的镜像。创建容器后,在容器内执行的命令如果导致容器内的数据产生变化,这些变更的数据会在容器删除的同时被清除,无法持久化保留。如果要解决持久化保留数据,可以采取两种方式:挂载容器的宿主环境(即执行启动容器命令所在的服务器)的目录或使用数据卷。数据卷可以理解为通过 Docker 引擎创建的宿主环境下的独立磁盘空间,用于在容器内读写数据,生命周期独立,不受容器生命周期的影响。
Docker 容器的网络有多种驱动类型,例如 bridge、host、overlay 等。其中默认的 bridge 类型类似网桥,用于点对点访问容器间端口或者将容器端口映射到宿主环境下。而 host 则是直接使用宿主环境的网络。更多网络模型介绍可参照官方说明文档。
在了解了容器化的基本概念后,我们再来看看什么是容器化的构建部署。
顾名思义,容器化的构建部署是把原先在部署服务器中执行的项目部署流程的各个环节,改为使用容器化的技术来完成,具体可以划分为操作镜像和操作容器两个阶段。
镜像阶段的主要目标是创建一个用于部署代码的容器环境的工作镜像。以前端项目为例,工作镜像中一般会包含:特定版本的 node 环境、git、项目构建所需的其他依赖库等。有了这样的环境就可以在对应的容器中执行各部署环节。
构建镜像的具体内容写在 Dockerfile 文件中,例如下面的代码:
# 通过FROM指定父镜像
FROM node:12-slim
# 通过RUN命令依次在镜像中安装git,make和curl程序
RUN apt-get update
RUN apt-get -y install git
RUN apt-get install -y build-essential
RUN apt-get install -y curl
这是一个基本的包含 node、git 等依赖程序的部署工作环境的镜像内容。
然后在 Dockerfile 所在目录下执行构建命令,即可创建相应镜像,如下所示:
docker build --network host --tag foo:bar .
容器阶段的主要目标就是基于项目的工作镜像创建执行部署过程的容器,并操作容器执行相应的各部署环节:获取代码、安装依赖、执行构建、产物打包、推送产物等。操作分为两个部分,创建容器与执行命令,如下面的代码所示:
# 创建容器
docker run -dit --name container_1 foo:bar bash
# 容器内执行命令
docker exec -it container_1 xxxx
与传统的在单台服务器上以目录区分不同部署项目的方式相比,容器化的构建部署过程有以下优点:
每个项目在独立的容器内执行构建部署过程,保证容器与宿主环境之间,容器与容器之间的环境隔离,防止原先共用一台服务器时可能产生的互相影响(例如项目脚本中修改了全局配置或环境变量等)。同时,环境隔离还可以保证每个项目都可以自由定制专属的环境依赖,而不必担心对其他项目产生影响。
使用容器化部署,可以方便地针对同一个项目生成多套不同的构建环境,达到类似 Github Actions 中的矩阵构建效果,使项目可以同时检测多套环境下的集成过程。
用户可通过 Xterm+SSH 的方式,通过浏览器访问部署系统中的容器环境。同传统的部署方式相比,用容器化的方式可以在部署遇到问题时让用户第一时间进入容器环境中进行现场调试,效率和便捷性大大提升。之前介绍过的部署工具 CircleCI 中就提供了这一调试功能。
由于部署过程的工作环境以 Docker 镜像的方式独立制作和存储,因此可以在支持 Docker 引擎的任意服务器中使用。使用时提供一致的工作环境,无须考虑不同服务器操作系统的差异。在迁移时也可以做到一键迁移,无须重复安装环境依赖。
尽管容器化的部署方式有上述优势,但也面临一些问题,例如缓存方面和性能方面的问题等。
项目在独立容器中构建部署时,首先面临的就是缓存方面的问题:
通常情况下,与传统的服务器部署相比,容器化部署并没有明显的性能差异,但是在实践中也存在一些性能方面的特殊情况:
这节课我们首先了解了以 Docker 为代表的容器化技术的基本概念:镜像、容器、数据挂载和网络等。然后讨论了容器化的构建部署需要经历的流程,先创建镜像,然后根据镜像创建容器,最后在容器内执行相关部署环节。接着分析了容器化部署的优势和劣势:容器化部署具有隔离性高、支持多环境矩阵执行、易于调试和环境标准化等优势,同时在使用时也需要额外注意对应的缓存和性能问题。
本节课的思考题是:容器化技术不仅可以应用在部署过程中,还更广泛地被应用在部署后的项目服务运行中。试比较这两种场景下对容器化技术需求的差异性。
下节课我们将进入部署效率模块的最后一节课:如何搭建基本的前端高效部署系统。